/*
  flamingo - 2D Game Engine
  Copyright (C) 2009 Bradley Zeis

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  Bradley Zeis
  bradzeis@gmail.com
*/

#define FLOPS       1
#define FLMATH      1
#define MESH        1

#include "flamingo.h"
#include "doc/flmath_doc.h"
#include "flmath.h"
#include <math.h>

PyTypeObject __declspec(dllexport) Vector_Type;
PyTypeObject __declspec(dllexport) Polygon_Type;
PyTypeObject __declspec(dllexport) Rect_Type;

/* -------- UTILITY -------- */
int _is_left(Vector *v1, Vector *v2, Vector *v3) {
    return ((v2->pt->x - v1->pt->x) * (v3->pt->y - v1->pt->y) -
            (v3->pt->x - v1->pt->x) * (v2->pt->y - v1->pt->y));
}

/* Vector */
Vector *uNewVector(double x, double y) {
    return (Vector *)PyObject_CallFunction((PyObject *)&Vector_Type, "((dd))", x, y);
}

double unormalize_angle(double x) {
    if (x > 0) {
        while (x >= 360)
            x -= 360;
        return x;
    }
    while (x < 0)
        x += 360;
    return x;
}

double udistance_sq(Vector *a, Vector *b) {
    return pow(b->pt->x - a->pt->y, 2) + pow(b->pt->y - a->pt->y, 2);
}

double uVector_length(Vector *v) {
    return sqrt(pow(v->pt->x, 2) + pow(v->pt->y, 2));
}

double udot(Vector *v1, Vector *v2) {
    return (v1->pt->x * v2->pt->x) + (v1->pt->y * v2->pt->y);
}

double uangle_between(Vector *v1, Vector *v2) {
    return RADTODEG(acos(udot(v1, v2) / (uVector_length(v1) * uVector_length(v2))));
}

/* Polygon/Rect */
Rect *uNewRect(double x, double y, double w, double h) {
    return (Rect *)PyObject_CallFunction((PyObject *)&Rect_Type, "(dd)dd", x, y, w, h);
}

PyObject *unormalize_poly(PyObject *poly, int wrap) {
    if (Polygon_Check(poly) == 0) {
        PyErr_SetString(PyExc_TypeError, "argument must be Polygon-like");
        return NULL;
    }

    PyObject *npoly = PySequence_List(poly);
    if (npoly == NULL) {
        PyErr_SetString(PyExc_TypeError, "argument must be Polygon-like");
        return NULL;
    }

    /* Calculate "base" poly*/
    PyObject *p0 = GETITEM(npoly, 0), *basepoly = NULL, *current;
    int i, j = PySequence_Length(npoly);

    for (i = 1; i < j; i++) {
        current = GETITEM(npoly, i);
        if (PyObject_RichCompareBool(current, p0, Py_EQ) == 1) {
            basepoly = PyList_GetSlice(npoly, 0, i);
            Py_DECREF(npoly);
            Py_DECREF(current);
            break;
        }
        Py_DECREF(current);
    }

    Py_DECREF(p0);

    // Poly already normalized
    if (basepoly == NULL) {
        basepoly = npoly;
    }

    /* Calculate "wrapped" poly */
    for (i = 0; i < wrap; i++) {
        current = GETITEM(basepoly, i);
        PyList_Append(basepoly, current);
        Py_DECREF(current);
    }

    return basepoly;
}

Vector *ucentroid(PyObject *seq) {
    PyObject *nseq = unormalize_poly(seq, 0);
    if (nseq == NULL)
        return NULL;

    int i = (int)PySequence_Length(nseq), j;
    double xt = 0, yt = 0;
    Vector *v;

    for (j = 0; j < i; j++) {
        v = Vector_FromObject(GETITEM(nseq, j));
        if (v == NULL) {
            PyErr_SetString(PyExc_TypeError, "argument must be Polygon-like");
            return NULL;
        }
        xt += v->pt->x;
        yt += v->pt->y;
        Py_DECREF(v);
    }

    Py_DECREF(nseq);
    return uNewVector(xt/i, yt/i);
}

int upoint_in(Vector *point, PyObject *poly) {
    int cn = 0, i;
    double vt;
    Vector *vi, *vii;
    int l = PySequence_Length(poly);
    
    for (i = 0; i < l-1; i++) {
        vi = Vector_FromObject(GETITEM(poly, i));
        vii = Vector_FromObject(GETITEM(poly, i+1));
    
        if ((vi->pt->y < point->pt->y && vii->pt->y >= point->pt->y) ||
            (vi->pt->y > point->pt->y && vii->pt->y <= point->pt->y)) {
            
            vt = point->pt->x - vi->pt->y / vii->pt->y - vi->pt->y;
            if (point->pt->x < vi->pt->x + vt * (vii->pt->x - vi->pt->x)) {
                cn += 1;
            }
            
        }
        
        Py_DECREF(vi);
        Py_DECREF(vii);
    }
    if (cn%2 != 0) {
        return 1;
    }
    return 0;
    
}

double usigned_area(PyObject *poly) {
    int l = PySequence_Length(poly);
    poly = unormalize_poly(poly, 2);
    if (poly == NULL)
        return 0;

    double sum = 0, a, b, c;
    int i;

    for (i = 1; i < l + 1; i++) {

        PyObject *ao, *bo, *co, *aao, *bbo, *cco;

        ao = GETITEM(poly, i);
        bo = GETITEM(poly, i+1);
        co = GETITEM(poly, i-1);

        aao = GETITEM(ao, 0);
        bbo = GETITEM(bo, 1);
        cco = GETITEM(co, 1);

        a = PyFloat_AsDouble(aao);
        b = PyFloat_AsDouble(bbo);
        c = PyFloat_AsDouble(cco);
        Py_DECREF(ao);
        Py_DECREF(bo);
        Py_DECREF(co);
        Py_DECREF(aao);
        Py_DECREF(bbo);
        Py_DECREF(cco);
        sum += a * (b - c);
    }
    sum = sum / 2.0;
    Py_DECREF(poly);
    return sum;
}

void uproject_to(Polygon *poly, Vector *axis, Projection *projection) {
    int i, l = poly->length;
    double results[l];
    Vector *point;

    for (i = 0; i < l; i++) {
        point = uNewVector(poly->points[i]->pt->x + poly->center->pt->x,
                           poly->points[i]->pt->y + poly->center->pt->y);
        results[i] = udot(point, axis);
        Py_DECREF(point);
    }

    projection->min = projection->max = results[0];

    for (i = 1; i < l; i++) {
        projection->min = MIN(results[i], projection->min);
        projection->max = MAX(results[i], projection->max);
    }
}

int uprojections_intersect(Projection *a, Projection *b) {
    if (a->max >= b->min && a->min <= b->max)
        return 0;
    return -1;
}

void unormperp(Vector *v) {
    double x = v->pt->x;
    v->pt->x = -(v->pt->y);
    v->pt->y = x;
    
    double l = uVector_length(v);
    v->pt->x /= l;
    v->pt->y /= l;
}

int upolys_intersect_rectopt(Rect *a, Rect *b) {
    double ax = a->center->pt->x - (a->width/2.0);
    double ay = a->center->pt->y - (a->height/2.0);
    double bx = b->center->pt->x - (b->width/2.0);
    double by = b->center->pt->y - (b->height/2.0);

    return ((ax >= bx && ax < bx + b->width)  ||
            (bx >= ax && bx < ax + a->width)) &&
           ((ay >= by && ay < by + b->height)  ||
            (by >= ay && by < ay + a->height));
}

int upolys_intersect(Polygon *a, Polygon *b) {
    // Still doesn't work, but there aren't memory leaks now! I think.
    //if ( ((a->ob_type == &Rect_Type) && (a->flags & POLYGON_AXIS_ALIGNED)) &&
    //     ((b->ob_type == &Rect_Type) && (b->flags & POLYGON_AXIS_ALIGNED)) ) {
    //    return upolys_intersect_rectopt((Rect *)a, (Rect *)b);
    //}
    //
    //int i, l1, l2;
    //l1 = (PyObject_TypeCheck(a, &Rect_Type) ? 2 : a->length);
    //l2 = (PyObject_TypeCheck(b, &Rect_Type) ? 2 : b->length);
    //Vector *e[l1 + l2];
    //
    //for (i = 0; i < l1; i++) {
    //    e[i] = uNewVector(a->edges[i]->pt->x, a->edges[i]->pt->y);
    //}
    //for (i = 0; i < l2; i++) {
    //    e[l1+i] = uNewVector(b->edges[i]->pt->x, b->edges[i]->pt->y);
    //}
    //
    //Projection ap, bp;
    //
    //for (i = 0; i < l1+l2; i++) {
    //    unormperp(e[i]);
    //    
    //    uproject_to(a, e[i], &ap);
    //    uproject_to(b, e[i], &bp);
    //
    //    if (uprojections_intersect(&ap, &bp) == -1) {
    //        int j;
    //        for (j = 0; j < l1+l2; j++) {
    //            Py_DECREF(e[j]);
    //        }
    //        return -1;
    //    }
    //}
    //
    // for (i = 0; i < l1+l2; i++) {
    //    Py_DECREF(e[i]);
    //}
    //
    //return 0;
    

    Point axis;
    double tmp, minA, maxA, minB, maxB;
    int side, i;
    
    /* test polygon A's sides */
    for (side = 0; side < a->length; side++) {
        /* get the axis that we will project onto */
        if (side == 0) {
            axis.x = a->points[a->length - 1]->pt->y - a->points[0]->pt->y;
            axis.y = a->points[0]->pt->x - a->points[a->length - 1]->pt->x;
        } else {
            axis.x = a->points[side - 1]->pt->y - a->points[side]->pt->y;
            axis.y = a->points[side]->pt->x - a->points[side - 1]->pt->x;
        }
    
        /* normalize the axis */
        tmp = sqrt(axis.x * axis.x + axis.y * axis.y);
        axis.x /= tmp;
        axis.y /= tmp;
    
        /* project polygon A onto axis to determine the min/max */
        minA = maxA = a->points[0]->pt->x * axis.x + a->points[0]->pt->y * axis.y;
        for (i = 1; i < a->length; i++) {
            tmp = a->points[i]->pt->x * axis.x + a->points[i]->pt->y * axis.y;
            if (tmp > maxA)
                maxA = tmp;
            else if (tmp < minA)
                minA = tmp;
        }
        /* correct for offset */
        tmp = a->center->pt->x * axis.x + a->center->pt->y * axis.y;
        minA += tmp;
        maxA += tmp;
    
        /* project polygon B onto axis to determine the min/max */
        minB = maxB = b->points[0]->pt->x * axis.x + b->points[0]->pt->y * axis.y;
        for (i = 1; i < b->length; i++) {
            tmp = b->points[i]->pt->x * axis.x + b->points[i]->pt->y * axis.y;
            if (tmp > maxB)
                maxB = tmp;
            else if (tmp < minB)
                minB = tmp;
        }
        /* correct for offset */
        tmp = b->center->pt->x * axis.x + b->center->pt->y * axis.y;
        minB += tmp;
        maxB += tmp;
    
        /* test if lines intersect, if not, return false */
        if (maxA < minB || minA > maxB)
            return -1;
    }
    
    /* test polygon B's sides */
    for (side = 0; side < b->length; side++)
    {
        /* get the axis that we will project onto */
        if (side == 0) {
            axis.x = b->points[b->length - 1]->pt->y - b->points[0]->pt->y;
            axis.y = b->points[0]->pt->x - b->points[b->length - 1]->pt->x;
        } else {
            axis.x = b->points[side - 1]->pt->y - b->points[side]->pt->y;
            axis.y = b->points[side]->pt->x - b->points[side - 1]->pt->x;
        }
    
        /* normalize the axis */
        tmp = sqrt(axis.x * axis.x + axis.y * axis.y);
        axis.x /= tmp;
        axis.y /= tmp;
    
        /* project polygon A onto axis to determine the min/max */
        minA = maxA = a->points[0]->pt->x * axis.x + a->points[0]->pt->y * axis.y;
        for (i = 1; i < a->length; i++) {
            tmp = a->points[i]->pt->x * axis.x + a->points[i]->pt->y * axis.y;
            if (tmp > maxA)
                maxA = tmp;
            else if (tmp < minA)
                minA = tmp;
        }
        /* correct for offset */
        tmp = a->center->pt->x * axis.x + a->center->pt->y * axis.y;
        minA += tmp;
        maxA += tmp;
    
        /* project polygon B onto axis to determine the min/max */
        minB = maxB = b->points[0]->pt->x * axis.x + b->points[0]->pt->y * axis.y;
        for (i = 1; i < b->length; i++) {
            tmp = b->points[i]->pt->x * axis.x + b->points[i]->pt->y * axis.y;
            if (tmp > maxB)
                maxB = tmp;
            else if (tmp < minB)
                minB = tmp;
        }
        /* correct for offset */
        tmp = b->center->pt->x * axis.x + b->center->pt->y * axis.y;
        minB += tmp;
        maxB += tmp;
    
        /* test if lines intersect, if not, return false */
        if (maxA < minB || minA > maxB)
            return -1;
    }
    
    return 0;
}

/* -------- CONVERSION -------- */
Vector *Vector_FromObject(PyObject *obj) {
    if (PyObject_TypeCheck(obj, &Vector_Type)) {
        return uNewVector(((Vector *)obj)->pt->x, ((Vector *)obj)->pt->y);
    }

    if (PyNumber_Check(obj)) {
        return uNewVector(PyFloat_AsDouble(obj), PyFloat_AsDouble(obj));
    }

    if (PySequence_Length(obj) >= 2) {
        PyObject *xo = GETITEM(obj, 0), *yo = GETITEM(obj, 1);
        double x = PyFloat_AsDouble(xo);
        double y = PyFloat_AsDouble(yo);
        Py_DECREF(xo);
        Py_DECREF(yo);
        return uNewVector(x, y);
    }

    PyErr_SetString(PyExc_TypeError, "arguments must be Vector-like");
    return NULL;
}

Polygon *Polygon_FromObject(PyObject *obj) {
    if (PyObject_TypeCheck(obj, &Rect_Type)) {
        return (Polygon *)PyObject_CallFunction((PyObject *)&Rect_Type, "(dd)dddb",
                                                ((Rect *)obj)->center->pt->x,
                                                ((Rect *)obj)->center->pt->y,
                                                ((Rect *)obj)->width,
                                                ((Rect *)obj)->height,
                                                ((Rect *)obj)->orientation,
                                                ((Rect *)obj)->flags);
    }

    if (PyObject_TypeCheck(obj, &Polygon_Type)) {
        PyObject *flags = Py_BuildValue("b", ((Polygon *)obj)->flags);
        PyObject *p = PyObject_CallFunctionObjArgs((PyObject *)&Polygon_Type,
                                                   Polygon_get_points((Polygon *)obj),
                                                   flags, NULL);
        Py_DECREF(flags);
        return (Polygon *)p;
    }

    if (!Polygon_Check(obj)) {
        PyErr_SetString(PyExc_TypeError, "arguments must be Polygon-like");
        return NULL;
    }

    return (Polygon *)PyObject_CallFunctionObjArgs((PyObject *)&Polygon_Type, obj, NULL);
}

/* -------- FLMATH FUNCTIONS -------- */
/* Generic Functions */
PyObject *closest_pow2(PyObject *self, PyObject *args) {
    double x;
    if (!PyArg_ParseTuple(args, "d", &x))
        return NULL;

    if (x < 0) {
        PyErr_SetString(PyExc_ValueError, "value must be positive");
        return NULL;
    }

    return Py_BuildValue("d", pow(2, floor(log(x)/log(2.0) + 0.5)));
}

PyObject *next_pow2(PyObject *self, PyObject *args) {
    double x;
    if (!PyArg_ParseTuple(args, "d", &x))
        return NULL;

    if (x < 0) {
        PyErr_SetString(PyExc_ValueError, "value must be positive");
        return NULL;
    }

    return Py_BuildValue("d", pow(2, ceil(log(x)/log(2.0))));
}

PyObject *prev_pow2(PyObject *self, PyObject *args) {
    double x;
    if (!PyArg_ParseTuple(args, "d", &x))
        return NULL;

    if (x < 0) {
        PyErr_SetString(PyExc_ValueError, "value must be positive");
        return NULL;
    }

    return Py_BuildValue("d", pow(2, floor(log(x)/log(2.0))));
}

PyObject *normalize_angle(PyObject *self, PyObject *args) {
    double x;
    if (!PyArg_ParseTuple(args, "d", &x))
        return NULL;
    return Py_BuildValue("d", unormalize_angle(x));
}

PyObject *normalize_angler(PyObject *self, PyObject *args) {
    double x;
    if (!PyArg_ParseTuple(args, "d", &x))
        return NULL;

    if (x > 0) {
        while (x >= 2 * PI)
            x -= 2 * PI;
    } else {
        while (x < 0)
            x += 2 * PI;
    }
    return Py_BuildValue("d", x);
}

/* Linear Functions */
PyObject *distance(PyObject *self, PyObject *args) {
    PyObject *p1, *p2;
    if (!PyArg_ParseTuple(args, "OO", &p1, &p2))
        return NULL;

    Vector *v1 = Vector_FromObject(p1);
    Vector *v2 = Vector_FromObject(p2);

    if (v1 == NULL || v2 == NULL) {
        PyErr_SetString(PyExc_TypeError, "arguments must be Vector-like");
        return NULL;
    }

    double d = sqrt(pow(v2->pt->x - v1->pt->x, 2) + pow(v2->pt->y - v1->pt->y, 2));
    Py_DECREF(v1);
    Py_DECREF(v2);
    return Py_BuildValue("d", d);
}

PyObject *distance_sq(PyObject *self, PyObject *args) {
    PyObject *p1, *p2;
    if (!PyArg_ParseTuple(args, "OO", &p1, &p2))
        return NULL;

    Vector *v1 = Vector_FromObject(p1);
    Vector *v2 = Vector_FromObject(p2);

    if (v1 == NULL || v2 == NULL) {
        PyErr_SetString(PyExc_TypeError, "arguments must be Vector-like");
        return NULL;
    }

    double d = pow(v2->pt->x - v1->pt->x, 2) + pow(v2->pt->y - v1->pt->y, 2);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return Py_BuildValue("d", d);
}

PyObject *midpoint(PyObject *self, PyObject *args) {
    PyObject *p1, *p2;
    if (!PyArg_ParseTuple(args, "OO", &p1, &p2))
        return NULL;

    Vector *v1 = Vector_FromObject(p1);
    Vector *v2 = Vector_FromObject(p2);

    if (v1 == NULL || v2 == NULL) {
        PyErr_SetString(PyExc_TypeError, "arguments must be Vector-like");
        return NULL;
    }

    v1->pt->x = (v1->pt->x + v2->pt->x) / 2.0;
    v1->pt->y = (v1->pt->y + v2->pt->y) / 2.0;
    Py_DECREF(v2);
    return Py_BuildValue("O", v1);
}

/* Vector Functions */
PyObject *dot(PyObject *self, PyObject *args) {
    PyObject *p1, *p2;
    if (!PyArg_ParseTuple(args, "OO", &p1, &p2))
        return NULL;

    Vector *v1 = Vector_FromObject(p1);
    Vector *v2 = Vector_FromObject(p2);

    if (v1 == NULL || v2 == NULL) {
        PyErr_SetString(PyExc_TypeError, "arguments must be Vector-like");
        return NULL;
    }

    double d = udot(v1, v2);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return Py_BuildValue("d", d);
}

PyObject *angle_between(PyObject *self, PyObject *args) {
    PyObject *p1, *p2;
    if (!PyArg_ParseTuple(args, "OO", &p1, &p2))
        return NULL;

    Vector *v1 = Vector_FromObject(p1);
    Vector *v2 = Vector_FromObject(p2);

    if (v1 == NULL || v2 == NULL) {
        PyErr_SetString(PyExc_TypeError, "arguments must be Vector-like");
        return NULL;
    }

    double a = uangle_between(v1, v2);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return Py_BuildValue("d", a);
}

/* Polygon Functions */
PyObject *normalize_poly(PyObject *self, PyObject *args) {
    PyObject *p;
    int wrap=0;
    if (!PyArg_ParseTuple(args, "O|i", &p, &wrap))
        return NULL;

    PyObject *poly = unormalize_poly(p, wrap);
    return poly;
}

PyObject *point_in(PyObject *self, PyObject *args) {
    PyObject *point, *p;
    if (!PyArg_ParseTuple(args, "OO", &point, &p))
        return NULL;

    point = (PyObject *)Vector_FromObject(point);
    PyObject *poly = unormalize_poly(p, 2);

    if (point == NULL)
        return NULL;
    if (poly == NULL)
        return NULL;

    int wn = upoint_in((Vector *)point, poly);
    Py_DECREF(poly);
    Py_DECREF(point);
    return PyBool_FromLong(wn);
}

PyObject *centroid(PyObject *self, PyObject *args) {
    PyObject *poly;
    if (!PyArg_ParseTuple(args, "O", &poly))
        return NULL;

    return (PyObject *)ucentroid(poly);
}

PyObject *area(PyObject *self, PyObject *args) {
    PyObject *poly;
    if (!PyArg_ParseTuple(args, "O", &poly))
        return NULL;

    double d = usigned_area(poly);
    if (d == 0) {
        return NULL;
    }
    return Py_BuildValue("d", fabs(d));
}

PyObject *signed_area(PyObject *self, PyObject *args) {
    PyObject *poly;
    if (!PyArg_ParseTuple(args, "O", &poly))
        return NULL;

    double d = usigned_area(poly);
    if (d == 0) {
        return NULL;
    }
    return Py_BuildValue("d", d);
}

PyObject *orientation_poly(PyObject *self, PyObject *args) {
    PyObject *poly;
    if (!PyArg_ParseTuple(args, "O", &poly))
        return NULL;

    double d = usigned_area(poly);

    if (d > 0) {
        Py_RETURN_TRUE;
    } else if (d < 0) {
        Py_RETURN_FALSE;
    } else {
        return NULL;
    }
}


/* -------- VECTOR METHODS -------- */
int Vector_Check(PyObject *obj) {
    if (PyObject_TypeCheck(obj, &Vector_Type))
        return 1;

    if (!PySequence_Check(obj))
        return 0;

    if (PySequence_Length(obj) < 2)
        return 0;

    if (!(PyNumber_Check(GETITEM(obj, 0)) && PyNumber_Check(GETITEM(obj, 1))))
        return 0;

    return 1;
}

/* Initialization */
void Vector_dealloc(Vector *self) {
    PyMem_Del(self->pt);
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Vector_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Vector *self;
    self = (Vector *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->pt = PyMem_New(Point, 1);
        self->pt->x = 0;
        self->pt->y = 0;
    }

    return (PyObject *)self;
}

int Vector_init(Vector *self, PyObject *args) {
    if (!PyArg_ParseTuple(args, "(dd)", &self->pt->x, &self->pt->y))
        return -1;
    return 0;
}

PyObject *Vector_reduce(Vector *self) {
    return Py_BuildValue("(O((dd)))", self->ob_type, (double)self->pt->x, (double)self->pt->y);
}

/* Presentation */
PyObject *Vector_repr(Vector *self) {
    char string[64];
    sprintf(string, "<flmath.Vector([%f, %f])>", self->pt->x, self->pt->y);
    return PyString_FromString(string);
}

PyObject *Vector_str(Vector *self) {
    return Vector_repr(self);
}

/* Comparison */
PyObject *Vector_richcompare(PyObject *a, PyObject *b, int op) {
    Vector *v1 = Vector_FromObject(a);
    Vector *v2 = Vector_FromObject(b);

    if (v1 == NULL || v2 == NULL) {
        PyErr_Format(PyExc_TypeError, "unsupported comparison for '%s' and '%s'",
                     v1->ob_type->tp_name, v2->ob_type->tp_name);
        return NULL;
    }

    switch (op) {
        case Py_EQ:
            if ((v1->pt->x == v2->pt->x) && (v1->pt->y == v2->pt->y)) {
                Py_DECREF(v1);
                Py_DECREF(v2);
                return Py_True;
            }
            Py_DECREF(v1);
            Py_DECREF(v2);
            return Py_False;

        case Py_NE:
            if ((v1->pt->x != v2->pt->x) || (v1->pt->y != v2->pt->y)) {
                Py_DECREF(v1);
                Py_DECREF(v2);
                return Py_True;
            }
            Py_DECREF(v1);
            Py_DECREF(v2);
            return Py_False;
        default:
            Py_DECREF(v1);
            Py_DECREF(v2);
            return Py_NotImplemented;
    }
}

/* Sequence Operations */
Py_ssize_t Vector_len(Vector *self) {
    return 2;
}

PyObject *Vector_item(Vector *self, Py_ssize_t i) {
    if (i == 0)
        return PyFloat_FromDouble(self->pt->x);
    if (i == 1)
        return PyFloat_FromDouble(self->pt->y);

    PyErr_SetString(PyExc_IndexError, "Vector index out of range");
    return NULL;
}

int Vector_ass_item(Vector *self, Py_ssize_t i, PyObject *value) {
    double val;
    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_ValueError, "Vector assignment must be numerical");
        return -1;
    }

    val = PyFloat_AsDouble(value);

    if (i == 0) {
        self->pt->x = val;
        return 0;
    }
    if (i == 1) {
        self->pt->y = val;
        return 0;
    }

    PyErr_SetString(PyExc_IndexError, "Vector index out of range");
    return -1;
}

/* Generic Number Operations */
Vector *Vector_oper(Vector *a, Vector *b, int oper) {
    /* Build Initial Values */
    Vector *v = uNewVector(0, 0);

    /* Perform Operations */
    switch (oper) {
        case ADD:
            v->pt->x = a->pt->x + b->pt->x;
            v->pt->y = a->pt->y + b->pt->y;
            break;
        case SUB:
            v->pt->x = a->pt->x - b->pt->x;
            v->pt->y = a->pt->y - b->pt->y;
            break;
        case MUL:
            v->pt->x = a->pt->x * b->pt->x;
            v->pt->y = a->pt->y * b->pt->y;
            break;
        case DIV:
            v->pt->x = a->pt->x / b->pt->x;
            v->pt->y = a->pt->y / b->pt->y;
            break;
        case FLR:
            v->pt->x = floor(a->pt->x / b->pt->x);
            v->pt->y = floor(a->pt->y / b->pt->y);
            break;
        case MOD:
            v->pt->x = fmod(a->pt->x, b->pt->x);
            v->pt->y = fmod(a->pt->y, b->pt->y);
            break;
        case POW:
            v->pt->x = pow(a->pt->x, b->pt->x);
            v->pt->y = pow(a->pt->y, b->pt->y);
            break;
    }
    return v;
}

void Vector_ioper(Vector *a, Vector *b, int oper) {

    /* Perform Operations */
    switch (oper) {
        case ADD:
            a->pt->x = a->pt->x + b->pt->x;
            a->pt->y = a->pt->y + b->pt->y;
            break;
        case SUB:
            a->pt->x = a->pt->x - b->pt->x;
            a->pt->y = a->pt->y - b->pt->y;
            break;
        case MUL:
            a->pt->x = a->pt->x * b->pt->x;
            a->pt->y = a->pt->y * b->pt->y;
            break;
        case DIV:
            a->pt->x = a->pt->x / b->pt->x;
            a->pt->y = a->pt->y / b->pt->y;
            break;
        case FLR:
            a->pt->x = floor(a->pt->x / b->pt->x);
            a->pt->y = floor(a->pt->y / b->pt->y);
            break;
        case MOD:
            a->pt->x = fmod(a->pt->x, b->pt->x);
            a->pt->y = fmod(a->pt->y, b->pt->y);
            break;
        case POW:
            a->pt->x = pow(a->pt->x, b->pt->x);
            a->pt->y = pow(a->pt->y, b->pt->y);
            break;
    }
}

PyObject *Vector_pos(Vector *self) {
    return (PyObject *)uNewVector(self->pt->x, self->pt->y);
}

PyObject *Vector_neg(Vector *self) {
    return (PyObject *)uNewVector(-(self->pt->x), -(self->pt->y));
}

PyObject *Vector_abs(Vector *self) {
    return (PyObject *)uNewVector(fabs(self->pt->x), fabs(self->pt->y));
}

int Vector_nonzero(Vector *self) {
    return self->pt->x != 0 && self->pt->y != 0;
}

/* Arithmetic Operations */
PyObject *Vector_add(PyObject *a, PyObject *b) {
    Vector *v1, *v2, *r;
    if ( ((v1 = Vector_FromObject(a)) == NULL) || ((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for +: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    r = Vector_oper(v1, v2, ADD);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return (PyObject *)r;
}

PyObject *Vector_iadd(Vector *a, PyObject *b) {
    Vector *v2;
    if (((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for +: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    Vector_ioper(a, v2, ADD);
    Py_DECREF(v2);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Vector_sub(PyObject *a, PyObject *b) {
    Vector *v1, *v2, *r;
    if ( ((v1 = Vector_FromObject(a)) == NULL) || ((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for -: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    r = Vector_oper(v1, v2, SUB);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return (PyObject *)r;
}

PyObject *Vector_isub(Vector *a, PyObject *b) {
    Vector *v2;
    if (((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for -: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    Vector_ioper(a, v2, SUB);
    Py_DECREF(v2);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Vector_mul(PyObject *a, PyObject *b) {
    Vector *v1, *v2, *r;
    if ( ((v1 = Vector_FromObject(a)) == NULL) || ((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for *: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    r = Vector_oper(v1, v2, MUL);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return (PyObject *)r;
}

PyObject *Vector_imul(Vector *a, PyObject *b) {
    Vector *v2;
    if (((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for *: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    Vector_ioper(a, v2, MUL);
    Py_DECREF(v2);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Vector_div(PyObject *a, PyObject *b) {
    Vector *v1, *v2, *r;
    if ( ((v1 = Vector_FromObject(a)) == NULL) || ((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for /: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    r = Vector_oper(v1, v2, DIV);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return (PyObject *)r;
}

PyObject *Vector_idiv(Vector *a, PyObject *b) {
    Vector *v2;
    if (((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for /: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    Vector_ioper(a, v2, DIV);
    Py_DECREF(v2);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Vector_flr(PyObject *a, PyObject *b) {
    Vector *v1, *v2, *r;
    if ( ((v1 = Vector_FromObject(a)) == NULL) || ((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for //: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    r = Vector_oper(v1, v2, FLR);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return (PyObject *)r;
}

PyObject *Vector_iflr(Vector *a, PyObject *b) {
    Vector *v2;
    if (((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for //: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    Vector_ioper(a, v2, FLR);
    Py_DECREF(v2);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Vector_mod(PyObject *a, PyObject *b) {
    Vector *v1, *v2, *r;
    if ( ((v1 = Vector_FromObject(a)) == NULL) || ((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for %%: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    r = Vector_oper(v1, v2, MOD);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return (PyObject *)r;
}

PyObject *Vector_imod(Vector *a, PyObject *b) {
    Vector *v2;
    if (((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for %%: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    Vector_ioper(a, v2, MOD);
    Py_DECREF(v2);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Vector_pow(PyObject *a, PyObject *b) {
    Vector *v1, *v2, *r;
    if ( ((v1 = Vector_FromObject(a)) == NULL) || ((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for **: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    r = Vector_oper(v1, v2, POW);
    Py_DECREF(v1);
    Py_DECREF(v2);
    return (PyObject *)r;
}

PyObject *Vector_ipow(Vector *a, PyObject *b) {
    Vector *v2;
    if (((v2 = Vector_FromObject(b)) == NULL)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for **: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    Vector_ioper(a, v2, POW);
    Py_DECREF(v2);
    Py_INCREF(a);
    return (PyObject *)a;
}

/* Generic Functions */
PyObject *Vector_copy(Vector *self) {
    return (PyObject *)uNewVector(self->pt->x, self->pt->y);
}

PyObject *Vector_update(Vector *self, PyObject *args) {
    PyObject *p;
    if (!PyArg_ParseTuple(args, "O", &p))
        return NULL;

    if (!(p = (PyObject *)Vector_FromObject(p))) {
        return NULL;
    }

    self->pt->x = ((Vector *)p)->pt->x;
    self->pt->y = ((Vector *)p)->pt->y;
    Py_DECREF(p);
    Py_RETURN_NONE;
}

PyObject *Vector_dot(Vector *self, PyObject *args) {
    PyObject *p;
    if (!PyArg_ParseTuple(args, "O", &p))
        return NULL;

    if (!(p = (PyObject *)Vector_FromObject(p))) {
        return NULL;
    }

    double d = udot(self, (Vector *)p);
    Py_DECREF(p);
    return Py_BuildValue("d", d);
}

PyObject *Vector_normalize(Vector *self) {
    Vector *v = uNewVector(self->pt->x, self->pt->y);
    Vector_setlength(v, PyFloat_FromDouble(1), CLOSURE);
    return (PyObject *)v;
}

PyObject *Vector_rotate(Vector *self, PyObject *args) {
    double angle;
    PyObject *p = NULL;
    if (!PyArg_ParseTuple(args, "d|O", &angle, &p))
        return NULL;

    if (p != NULL) {
        if (!(p = (PyObject *)Vector_FromObject(p)))
            return NULL;
    } else {
        p = (PyObject *)uNewVector(0, 0);
    }

    Vector *v = uNewVector(self->pt->x, self->pt->y);
    PyObject *val = PyTuple_Pack(2,
                      PyFloat_FromDouble(angle + PyFloat_AsDouble(
                        Vector_getangle(v, CLOSURE))), p);
    Vector_setangle(v, val, CLOSURE);
    Py_DECREF(p);
    return (PyObject *)v;
}

PyObject *Vector_perpendicular(Vector *self) {
    return (PyObject *)uNewVector(-(self->pt->y), self->pt->x);
}

PyObject *Vector_angle_about(Vector *self, PyObject *args) {
    PyObject *other;
    if (!PyArg_ParseTuple(args, "O", &other))
        return NULL;

    if (!(other = (PyObject *)Vector_FromObject(other)))
        return NULL;

    Vector *v = uNewVector(self->pt->x, self->pt->y);
    v->pt->x -= ((Vector *)other)->pt->x;
    v->pt->y -= ((Vector *)other)->pt->y;
    double angle = RADTODEG(atan2(v->pt->y, v->pt->x));
    Py_DECREF(other);
    return Py_BuildValue("d", angle);
}

PyObject *Vector_angle_between(Vector *self, PyObject *args) {
    PyObject *other;
    if (!PyArg_ParseTuple(args, "O", &other))
        return NULL;

    if (!(other = (PyObject *)Vector_FromObject(other)))
        return NULL;

    PyObject *a = Py_BuildValue("d", uangle_between(self, (Vector *)other));
    Py_DECREF(other);
    return a;
}

/* Properties */
PyObject *Vector_getx(Vector *self, void *closure) {
    return Py_BuildValue("d", self->pt->x);
}

int Vector_setx(Vector *self, PyObject *value, void *closure) {
    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "Vector length must be numerical");
        return -1;
    }

    double val;
    if (!(val = PyFloat_AsDouble(value)))
        return -1;
    self->pt->x = val;
    return 0;
}

PyObject *Vector_gety(Vector *self, void *closure) {
    return Py_BuildValue("d", self->pt->y);
}

int Vector_sety(Vector *self, PyObject *value, void *closure) {
    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "Vector length must be numerical");
        return -1;
    }

    double val;
    if (!(val = PyFloat_AsDouble(value)))
        return -1;
    self->pt->y = val;
    return 0;
}

/* Length */
PyObject *Vector_getlength(Vector *self, void *closure) {
    return Py_BuildValue("d", uVector_length(self));
}

int Vector_setlength(Vector *self, PyObject *value, void *closure) {
    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "Vector length must be numerical");
        return -1;
    }

    double val;
    if (!(val = PyFloat_AsDouble(value)))
        return -1;

    double l = val / uVector_length(self);
    self->pt->x *= l;
    self->pt->y *= l;
    return 0;
}

/* Angle */
PyObject *Vector_getangle(Vector *self, void *closure) {
    return Py_BuildValue("d", RADTODEG(atan2(self->pt->y, self->pt->x)));
}

int Vector_setangle(Vector *self, PyObject *value, void *closure) {
    double angle;
    PyObject *point = NULL;

    if (!PyTuple_Check(value))
        value = PyTuple_Pack(1, value);

    if (!PyArg_ParseTuple(value, "d|O", &angle, &point))
        return -1;

    if (point != NULL) {
        if(!(point = (PyObject *)Vector_FromObject(point)))
            return -1;

        self->pt->x -= ((Vector *)point)->pt->x;
        self->pt->y -= ((Vector *)point)->pt->y;

    } else {
        point = (PyObject *)uNewVector(0, 0);
    }
    
    double len = uVector_length(self);
    self->pt->x = len;
    self->pt->y = 0;

    angle = unormalize_angle(angle);
    double x, y;

    if (angle == 0) {
        return 0;
    } else if (angle == 90) {
        x = -self->pt->y;
        y = self->pt->x;
        self->pt->x = x;
        self->pt->y = y;
        self->pt->x += ((Vector *)point)->pt->x;
        self->pt->y += ((Vector *)point)->pt->y;
        return 0;
    } else if (angle == 180) {
        x = -self->pt->x;
        y = -self->pt->y;
        self->pt->x = x;
        self->pt->y = y;
        self->pt->x += ((Vector *)point)->pt->x;
        self->pt->y += ((Vector *)point)->pt->y;
        return 0;
    } else if (angle == 270) {
        x = self->pt->y;
        y = -self->pt->x;
        self->pt->x = x;
        self->pt->y = y;
        self->pt->x += ((Vector *)point)->pt->x;
        self->pt->y += ((Vector *)point)->pt->y;
        return 0;
    }

    angle = DEGTORAD(angle);
    x = len * cos(angle);
    y = len * sin(angle);
    self->pt->x = x;
    self->pt->y = y;
    self->pt->x += ((Vector *)point)->pt->x;
    self->pt->y += ((Vector *)point)->pt->y;
    return 0;
}

PyMemberDef Vector_members[] = {
    {NULL}
};

PySequenceMethods Vector_SequenceMethods = {
    (lenfunc)Vector_len,                        /* sq_length */
    0,                                          /* sq_concat */
    0,                                          /* sq_repeat */
    (ssizeargfunc)Vector_item,                  /* sq_item */
    0,
    (ssizeobjargproc)Vector_ass_item,           /* sq_ass_item */
    0,                                          /* sq_contains */
    0,                                          /* sq_inplace_concat */
    0,                                          /* sq_inplace_repeat */
};

PyNumberMethods Vector_NumberMethods = {
    (binaryfunc)Vector_add,                     /* nb_add */
    (binaryfunc)Vector_sub,                     /* nb_subtract */
    (binaryfunc)Vector_mul,                     /* nb_multiply */
    (binaryfunc)Vector_div,                     /* nb_divide */
    (binaryfunc)Vector_mod,                     /* nb_remainder */
    0,                                          /* nb_divmod */
    (ternaryfunc)Vector_pow,                    /* nb_power */
    (unaryfunc)Vector_neg,                      /* nb_negative */
    (unaryfunc)Vector_pos,                      /* nb_positive */
    (unaryfunc)Vector_abs,                      /* nb_absolute */
    (inquiry)Vector_nonzero,                    /* nb_nonzero */
    0,                                          /* nb_invert */
    0,                                          /* nb_lshift */
    0,                                          /* nb_rshift */
    0,                                          /* nb_and */
    0,                                          /* nb_xor */
    0,                                          /* nb_or */
    0,                                          /* nb_coerce */
    0,                                          /* nb_int */
    0,                                          /* nb_long */
    0,                                          /* nb_float */
    0,                                          /* nb_oct */
    0,                                          /* nb_hex */
    (binaryfunc)Vector_iadd,                /* nb_inplace_add */
    (binaryfunc)Vector_isub,                /* nb_inplace_subtract */
    (binaryfunc)Vector_imul,                /* nb_inplace_multiply */
    (binaryfunc)Vector_idiv,                /* nb_inplace_divide */
    (binaryfunc)Vector_imod,                /* nb_inplace_remainder */
    (ternaryfunc)Vector_ipow,               /* nb_inplace_power */
    0,                                      /* nb_inplace_lshift */
    0,                                      /* nb_inplace_rshift */
    0,                                      /* nb_inplace_and */
    0,                                      /* nb_inplace_xor */
    0,                                      /* nb_inplace_or */
    (binaryfunc)Vector_flr,                     /* nb_floor_divide */
    (binaryfunc)Vector_div,                     /* nb_true_divide */
    (binaryfunc)Vector_iflr,                    /* nb_inplace_floor_divide */
    (binaryfunc)Vector_idiv,                    /* nb_inplace_true_divide */
    0,                                      /* nb_index */
};

PyMethodDef Vector_methods[] = {
    {"copy", (PyCFunction)Vector_copy, METH_NOARGS, vect_copy_doc},
    {"update", (PyCFunction)Vector_update, METH_VARARGS, vect_updt_doc},
    {"dot", (PyCFunction)Vector_dot, METH_VARARGS, vect_dot_doc},
    {"normalize", (PyCFunction)Vector_normalize, METH_NOARGS, vect_norm_doc},
    {"rotate", (PyCFunction)Vector_rotate, METH_VARARGS, vect_rota_doc},
    {"perpendicular", (PyCFunction)Vector_perpendicular, METH_NOARGS, vect_perp_doc},
    {"angle_about", (PyCFunction)Vector_angle_about, METH_VARARGS, vect_anga_doc},
    {"angle_between", (PyCFunction)Vector_angle_between, METH_VARARGS, vect_angb_doc},
    {"__reduce__", (PyCFunction)Vector_reduce, METH_NOARGS, NULL},
    {NULL}
};

PyGetSetDef Vector_getsets[] = {
    {"x", (getter)Vector_getx, (setter)Vector_setx, NULL, vect_x_doc},
    {"y", (getter)Vector_gety, (setter)Vector_sety, NULL, vect_y_doc},
    {"length", (getter)Vector_getlength, (setter)Vector_setlength, NULL, vect_leng_doc},
    {"angle", (getter)Vector_getangle, (setter)Vector_setangle, NULL, vect_angl_doc},
    {NULL}
};

/* -------- POLYGON METHODS -------- */
int Polygon_Check(PyObject *obj) {
    if (PyObject_TypeCheck(obj, &Polygon_Type))
        return 1;

    if (PySequence_Check(obj) == 0) {
        return 0;
    }

    int i, j = PySequence_Length(obj);

    if (j < 3)
        return 0;

    for (i = 0; i < j; i++) {
        if (Vector_Check(GETITEM(obj, i)) == 0)
            return 0;
    }
    return 1;
}

/* Initialization */
void Polygon_dealloc(Polygon *self) {
    Py_XDECREF(self->center);
    int i;
    for (i = 0; i < self->length; i++) {
        Py_XDECREF(self->points[i]);
        Py_XDECREF(self->edges[i]);
    }
    PyMem_Del(self->points);
    PyMem_Del(self->edges);
    PyMem_Del(self->orients);
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Polygon_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Polygon *self;
    self = (Polygon *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->center = uNewVector(0, 0);
        if (self->center == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->points = NULL;
        self->edges = NULL;
        self->orients = NULL;

        self->orientation = 0;
        self->flags = 0;
    }

    return (PyObject *)self;
}

int Polygon_init(Polygon *self, PyObject *args) {

    PyObject *verts;
    unsigned char flags=0;
    if (!PyArg_ParseTuple(args, "O|b", &verts, &flags))
        return -1;

    /* Sanitize input */
    verts = unormalize_poly(verts, 0);
    if (verts == NULL)
        return -1;

    if (!(self->center = ucentroid(verts)))
        return -1;

    /* Initialize internal arrays */
    self->length = PySequence_Length(verts);
    self->flags = flags;

    self->points = PyMem_New(Vector *, self->length);
    self->edges = PyMem_New(Vector *, self->length);
    self->orients = PyMem_New(double, self->length);

    if ((self->points == NULL) || (self->edges == NULL) || (self->orients == NULL)) {
        PyErr_SetNone(PyExc_MemoryError); 
        return -1;
    }

    /* Calculate all points */
    int i;
    Vector *v, *res;

    for (i = 0; i < self->length; i++) {
        v = (Vector *)GETITEM(verts, i);
        if (!(res = (Vector *)Vector_sub((PyObject *)v, (PyObject *)self->center))) {
            PyErr_SetString(PyExc_TypeError, "argument must be Polygon-like");
            return -1;
        }

        self->points[i] = uNewVector(res->pt->x, res->pt->y);
        self->edges[i] = NULL;
        Py_DECREF(v);
        Py_DECREF(res);
    }

    PyObject *val = Py_BuildValue("(i)", 0);
    PyObject *result = Polygon_setcache(self, val);
    Py_DECREF(val);
    Py_DECREF(result);
    return 0;
}

PyObject *Polygon_setcache(Polygon *self, PyObject *args) {
    int update_center;
    if (!PyArg_ParseTuple(args, "i", &update_center))
        return NULL;

    int i;
    Vector *point, *next_point, *edge;
    PyObject *temp;
    double orient;

    /* Calculate new information */
    for (i = 0; i < self->length; i++) {
        /* Calculate */
        point = self->points[i];
        next_point = self->points[(i+1)%self->length];
        edge = (Vector *)Vector_sub((PyObject *)next_point, (PyObject *)point);
        temp = Vector_getangle(point, CLOSURE);
        orient = PyFloat_AS_DOUBLE(temp) - self->orientation;
        
        Py_DECREF(temp);

        /* Set new information */
        Py_XDECREF(self->edges[i]);
        self->edges[i] = edge;
        self->orients[i] = orient;
    }

    if (update_center == 1) {
        PyObject *points;
        Vector *centroid;
        points = Polygon_get_points(self);
        centroid = ucentroid(points);

        self->center->pt->x = centroid->pt->x;
        self->center->pt->y = centroid->pt->y;

        Py_DECREF(points);
        Py_DECREF(centroid);
    }
    Py_RETURN_NONE;
}

PyObject *Polygon_reduce(PyObject *self) {
    return Py_BuildValue("(O(Ob))", self->ob_type,
                         Polygon_get_points((Polygon *)self), ((Polygon *)self)->flags);
}

PyObject *Polygon_enableflag(Polygon *self, PyObject *args) {
    unsigned char flag;
    if (!PyArg_ParseTuple(args, "b", &flag))
        return NULL;

    if ((flag == POLYGON_AXIS_ALIGNED) && (self->orientation != 0)) {
        printf("boi!");
        PyObject *val = Py_BuildValue("i", 0);
        Polygon_setorientation(self, val, CLOSURE);
        Py_DECREF(val);
    }

    self->flags = self->flags | flag;
    Py_RETURN_NONE;
}

PyObject *Polygon_disableflag(Polygon *self, PyObject *args) {
    unsigned char flag;
    if (!PyArg_ParseTuple(args, "b", &flag))
        return NULL;

    self->flags = self->flags & ~flag;
    Py_RETURN_NONE;
}

PyObject *Polygon_flagenabled(Polygon *self, PyObject *args) {
    unsigned char flag;
    if (!PyArg_ParseTuple(args, "b", &flag))
        return NULL;

    if (self->flags & flag)
        Py_RETURN_TRUE;
    Py_RETURN_FALSE;
}

/* Presentation */
PyObject *Polygon_repr(Polygon *self) {
    char string[64];
    sprintf(string, "<flmath.Polygon([%f, %f], %d sides)>", self->center->pt->x,
            self->center->pt->y, self->length);
    return PyString_FromString(string);
}

PyObject *Polygon_str(Polygon *self) {
    return Polygon_repr(self);
}

/* Comparison */
PyObject *Polygon_richcompare(PyObject *a, PyObject *b, int op) {
    Polygon *p1, *p2;

    if (!PyObject_TypeCheck(a, &Polygon_Type)) {
        p1 = Polygon_FromObject(a);
    } else {
        p1 = (Polygon *)a;
        Py_INCREF(p1);
    }

    if (!PyObject_TypeCheck(b, &Polygon_Type)) {
        p2 = Polygon_FromObject(b);
    } else {
        p2 = (Polygon *)b;
        Py_INCREF(p2);
    }

    if (p1 == NULL || p2 == NULL) {
        PyErr_Format(PyExc_TypeError, "unsupported comparison for '%s' and '%s'",
                     p1->ob_type->tp_name, p2->ob_type->tp_name);
        return NULL;
    }

    switch (op) {
        case Py_EQ:
            if ((PyObject_RichCompareBool(Polygon_get_points(p1), Polygon_get_points(p2), Py_EQ) == 1)) {
                Py_DECREF(p1);
                Py_DECREF(p2);
                return Py_True;
            }
            Py_DECREF(p1);
            Py_DECREF(p2);
            return Py_False;

        case Py_NE:
            if ((PyObject_RichCompareBool(Polygon_get_points(p1), Polygon_get_points(p2), Py_NE) == 1)) {
                Py_DECREF(p1);
                Py_DECREF(p2);
                return Py_True;
            }
            Py_DECREF(p1);
            Py_DECREF(p2);
            return Py_False;
        default:
            Py_DECREF(p1);
            Py_DECREF(p2);
            return Py_NotImplemented;
    }
}

/* Sequence Operations */
Py_ssize_t Polygon_len(Polygon *self) {
    return self->length;
}

PyObject *Polygon_item(Polygon *self, Py_ssize_t i) {
    if (i >= self->length || i < 0) {
        PyErr_SetString(PyExc_IndexError, "Polygon index out of range");
        return NULL;
    }
    return Vector_add((PyObject *)self->points[i], (PyObject *)self->center);
}

PyObject *Polygon_node(Polygon *self, PyObject *args) {

    int i;
    if (!PyArg_ParseTuple(args, "i", &i))
        return NULL;

    if (i >= self->length || i < 0) {
        PyErr_SetString(PyExc_IndexError, "Polygon index out of range");
        return NULL;
    }

    Py_INCREF(self->points[i]);
    return (PyObject *)self->points[i];
}

void Polygon_reposition(Polygon *self) {
    // When a vertex is changed, the center is moved, invalidating the other vertices.
    // Subtracting the difference between the previous center and the current center
    // from all of the vertices fixes this problem.

    double ocx = self->center->pt->x, ocy = self->center->pt->y;
    PyObject *val = Py_BuildValue("(i)", 1);
    PyObject *result = Polygon_setcache(self, val);
    Py_DECREF(val);
    Py_DECREF(result);
    Vector *d = uNewVector(self->center->pt->x - ocx, self->center->pt->y - ocy);

    int i;
    for (i = 0; i < self->length; i++) {
        self->points[i]->pt->x -= d->pt->x;
        self->points[i]->pt->y -= d->pt->y;
    }

    Py_DECREF(d);
}

int Polygon_ass_item(Polygon *self, Py_ssize_t i, PyObject *value) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    if (i >= self->length || i < 0) {
        PyErr_SetString(PyExc_IndexError, "Polygon index out of range");
        return -1;
    }

    Vector *v = Vector_FromObject(value);
    if (v == NULL) {
        PyErr_SetString(PyExc_TypeError, "argument must be Vector-like");
        return -1;
    }

    self->points[i]->pt->x = v->pt->x - self->center->pt->x;
    self->points[i]->pt->y = v->pt->y - self->center->pt->y;
    Py_DECREF(v);

    Polygon_reposition(self);
    return 0;
}

PyObject *Polygon_append(Polygon *self, PyObject *args) {
    if (!(self->flags & POLYGON_MUTABLE_LENGTH)) {
        PyErr_SetString(PyExc_AttributeError, "can't change length of this Polygon");
        return NULL;
    }

    if ((self->flags & POLYGON_STATIC)) {
        PyErr_SetString(PyExc_AttributeError, "can't change length of this Polygon");
        return NULL;
    }

    Py_RETURN_NONE;
}

PyObject *Polygon_insert(Polygon *self, PyObject *args) {
    if (!(self->flags & POLYGON_MUTABLE_LENGTH)) {
        PyErr_SetString(PyExc_AttributeError, "can't change length of this Polygon");
        return NULL;
    }

    if ((self->flags & POLYGON_STATIC)) {
        PyErr_SetString(PyExc_AttributeError, "can't change length of this Polygon");
        return NULL;
    }

    Py_RETURN_NONE;
}

PyObject *Polygon_remove(Polygon *self, PyObject *args) {
    if (!(self->flags & POLYGON_MUTABLE_LENGTH)) {
        PyErr_SetString(PyExc_AttributeError, "can't change length of this Polygon");
        return NULL;
    }

    if ((self->flags & POLYGON_STATIC)) {
        PyErr_SetString(PyExc_AttributeError, "can't change length of this Polygon");
        return NULL;
    }

    if (self->length == 3) {
        PyErr_SetString(PyExc_IndexError, "Polygon can't have less than 3 Vertices");
        return NULL;
    }

    Py_RETURN_NONE;
}

/* Generic Functions */
PyObject *Polygon_copy(Polygon *self) {
    return (PyObject *)Polygon_FromObject((PyObject *)self);
}

PyObject *Polygon_get_points(Polygon *self) {
    PyObject *npoints = PyTuple_New(self->length), *p;
    int i;

    for (i = 0; i < self->length; i++) {
        p = Vector_add((PyObject *)self->points[i], (PyObject *)self->center);
        PyTuple_SetItem(npoints, i, p);
    }
    return npoints;
}

PyObject *Polygon_point_in(Polygon *self, PyObject *args) {
    PyObject *point, *points, *npoints;
    if (!PyArg_ParseTuple(args, "O", &point))
        return NULL;

    point = (PyObject *)Vector_FromObject(point);
    if (point == NULL)
        return NULL;

    points = Polygon_get_points(self);
    npoints = unormalize_poly(points, 2);
    if (npoints == NULL)
        return NULL;

    int wn = upoint_in((Vector *)point, npoints);
    Py_DECREF(npoints);
    Py_DECREF(points);
    Py_DECREF(point);
    if (wn != 0) {
        Py_RETURN_TRUE;
    }
    Py_RETURN_FALSE;
}

PyObject *Polygon_project_to(Polygon *self, PyObject *args) {
    PyObject *a;
    if (!PyArg_ParseTuple(args, "O", &a))
        return NULL;

    Vector *axis = Vector_FromObject(a);
    if (axis == NULL)
        return NULL;

    Projection p;
    uproject_to(self, axis, &p);
    Py_DECREF(axis);
    return Py_BuildValue("dd", p.min, p.max);
}

PyObject *Polygon_angle_about(Polygon *self, PyObject *args) {
    return Vector_angle_about(self->center, args);
}

PyObject *Polygon_intersects(Polygon *self, PyObject *args) {
    PyObject *p;
    if (!PyArg_ParseTuple(args, "O", &p))
        return NULL;

    int i;
    if (PyObject_TypeCheck(p, &Polygon_Type)) {
        i = upolys_intersect(self, (Polygon *)p);
    } else {
        if (!Polygon_Check(p)) {
            PyErr_Format(PyExc_TypeError, "argument must be 'flamingo.flmath.Polygon', not '%s'",
                         p->ob_type->tp_name);
            return NULL;
        }

        Polygon *poly = Polygon_FromObject(p);
        i = upolys_intersect(self, poly);
        Py_DECREF(poly);
    }

    if (i == 0) {
        Py_RETURN_TRUE;
    } else if (i == -1) {
        Py_RETURN_FALSE;
    } else {
        return NULL;
    }
}

/* Properties */
PyObject *Polygon_getcenter(Polygon *self, void *closure) {
    return (PyObject *)Vector_FromObject((PyObject *)self->center);
}

int Polygon_setcenter(Polygon *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    Vector *v;
    if (!(v = Vector_FromObject(value)))
        return -1;
    self->center->pt->x = v->pt->x;
    self->center->pt->y = v->pt->y;
    Py_DECREF(v);
    return 0;
}

PyObject *Polygon_getcenterx(Polygon *self, void *closure) {
    return PyFloat_FromDouble(self->center->pt->x);
}

int Polygon_setcenterx(Polygon *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    if (!PyNumber_Check(value))
        return -1;
    double v = PyFloat_AsDouble(value);
    self->center->pt->x = v;
    return 0;
}

PyObject *Polygon_getcentery(Polygon *self, void *closure) {
    return PyFloat_FromDouble(self->center->pt->y);
}

int Polygon_setcentery(Polygon *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    if (!PyNumber_Check(value))
        return -1;
    double v = PyFloat_AsDouble(value);
    self->center->pt->y = v;
    return 0;
}

PyObject *Polygon_getarea(Polygon *self, void *closure) {
    return Py_BuildValue("d", fabs(usigned_area((PyObject *)self)));
}

PyObject *Polygon_getsarea(Polygon *self, void *closure) {
    return Py_BuildValue("d", usigned_area((PyObject *)self));
}

PyObject *Polygon_getangle(Polygon *self, void *closure) {
    return Vector_getangle(self->center, closure);
}

int Polygon_setangle(Polygon *self, PyObject *value, void *closure) {
    if (self->flags & POLYGON_STATIC)
        return 0;

    return Vector_setangle(self->center, value, closure);
}

PyObject *Polygon_getorientation(Polygon *self, void *closure) {
    return Py_BuildValue("d", self->orientation);
}

int Polygon_setorientation(Polygon *self, PyObject *value, void *closure) {

    if ((self->flags & POLYGON_STATIC) || (self->flags & POLYGON_AXIS_ALIGNED))
        return 0;

    if (!PyNumber_Check(value))
        return -1;

    int i;
    Vector *v;
    double orient;
    PyObject *val;

    for (i = 0; i < self->length; i++) {
        orient = self->orients[i];
        v = self->points[i];
        val = Py_BuildValue("d", orient + PyFloat_AsDouble(value));
        Vector_setangle(v, val, CLOSURE);
        Py_DECREF(val);
    }

    self->orientation = PyFloat_AsDouble(value);
    val = Py_BuildValue("(i)", 0);
    PyObject *result = Polygon_setcache((Polygon *)self, val);
    Py_DECREF(val);
    Py_DECREF(result);
    return 0;
}

/* Method Declaration */
PyMemberDef Polygon_members[] = {
    {NULL}
};

PySequenceMethods Polygon_SequenceMethods = {
    (lenfunc)Polygon_len,                       /* sq_length */
    0,                                          /* sq_concat */
    0,                                          /* sq_repeat */
    (ssizeargfunc)Polygon_item,                 /* sq_item */
    0,
    (ssizeobjargproc)Polygon_ass_item,          /* sq_ass_item */
    0,                                          /* sq_contains */
    0,                                          /* sq_inplace_concat */
    0,                                          /* sq_inplace_repeat */
};

PyMethodDef Polygon_methods[] = {
    {"_setcache", (PyCFunction)Polygon_setcache, METH_VARARGS, NULL},
    {"enable", (PyCFunction)Polygon_enableflag, METH_VARARGS, NULL},
    {"disable", (PyCFunction)Polygon_disableflag, METH_VARARGS, NULL},
    {"enabled", (PyCFunction)Polygon_flagenabled, METH_VARARGS, NULL},
    {"append", (PyCFunction)Polygon_append, METH_VARARGS, NULL},
    {"insert", (PyCFunction)Polygon_insert, METH_VARARGS, NULL},
    {"remove", (PyCFunction)Polygon_remove, METH_VARARGS, NULL},
    {"copy", (PyCFunction)Polygon_copy, METH_NOARGS, poly_copy_doc},
    {"get_points", (PyCFunction)Polygon_get_points, METH_NOARGS, poly_gtpt_doc},
    {"node", (PyCFunction)Polygon_node, METH_VARARGS, NULL},
    {"point_in", (PyCFunction)Polygon_point_in, METH_VARARGS, poly_ptin_doc},
    {"project_to", (PyCFunction)Polygon_project_to, METH_VARARGS, poly_proj_doc},
    {"angle_about", (PyCFunction)Polygon_angle_about, METH_VARARGS, poly_anga_doc},
    {"intersects", (PyCFunction)Polygon_intersects, METH_VARARGS, poly_intr_doc},
    {"__reduce__", (PyCFunction)Polygon_reduce, METH_VARARGS, NULL},
    {NULL}
};

PyGetSetDef Polygon_getsets[] = {
    {"center", (getter)Polygon_getcenter, (setter)Polygon_setcenter, NULL, poly_cent_doc},
    {"centerx", (getter)Polygon_getcenterx, (setter)Polygon_setcenterx, NULL, poly_cenx_doc},
    {"centery", (getter)Polygon_getcentery, (setter)Polygon_setcentery, NULL, poly_ceny_doc},
    {"area", (getter)Polygon_getarea, NULL, NULL, poly_area_doc},
    {"sarea", (getter)Polygon_getsarea, NULL, NULL, poly_sarea_doc},
    {"angle", (getter)Polygon_getangle, (setter)Polygon_setangle, NULL, poly_angl_doc},
    {"orientation", (getter)Polygon_getorientation, (setter)Polygon_setorientation, NULL, poly_orie_doc},
    {NULL}
};

/* -------- RECT METHODS -------- */
/* Initialization */
void Rect_dealloc(Rect *self) {
    Py_XDECREF(self->center);
    int i;
    for (i = 0; i < self->length; i++) {
        Py_XDECREF(self->points[i]);
        Py_XDECREF(self->edges[i]);
    }
    PyMem_Del(self->points);
    PyMem_Del(self->edges);
    PyMem_Del(self->orients);
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Rect_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Rect *self;
    self = (Rect *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->center = uNewVector(0, 0);
        if (self->center == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->points = NULL;
        self->edges = NULL;
        self->orients = NULL;

        self->length = 4;
        self->width = 0;
        self->height = 0;
        self->orientation = 0;
        self->flags = 0;
    }

    return (PyObject *)self;
}

int Rect_init(Rect *self, PyObject *args) {
    /* Initialize internal arrays */
    self->points = PyMem_New(Vector *, self->length);
    self->edges = PyMem_New(Vector *, self->length);
    self->orients = PyMem_New(double, self->length);

    self->points[0] = NULL;
    self->points[1] = NULL;
    self->points[2] = NULL;
    self->points[3] = NULL;
    self->edges[0] = NULL;
    self->edges[1] = NULL;
    self->edges[2] = NULL;
    self->edges[3] = NULL;

    /* Parse arguments */
    PyObject *center;
    double orientation = 0;
    if (!PyArg_ParseTuple(args, "Odd|db", &center, &self->width, &self->height, &orientation, &self->flags))
        return -1;

    self->center = Vector_FromObject(center);
    if (self->center == NULL) {
        return -1;
    }

    self->orientation = orientation;

    if ((self->points == NULL) || (self->edges == NULL) || (self->orients == NULL)) {
        PyErr_Format(PyExc_MemoryError, "ran out of memory");
        return -1;
    }

    PyObject *result = Rect_calculate_corners(self);
    Py_DECREF(result);
    PyObject *val = Py_BuildValue("(i)", 0);
    result = Polygon_setcache((Polygon *)self, val);
    Py_DECREF(val);
    Py_DECREF(result);
    return 0;
}

PyObject *Rect_calculate_corners(Rect *self) {
    float w = self->width / 2, h = self->height / 2;
    Py_XDECREF(self->points[0]);
    self->points[0] = uNewVector(-w, -h);
    Py_XDECREF(self->points[1]);
    self->points[1] = uNewVector(w, -h);
    Py_XDECREF(self->points[2]);
    self->points[2] = uNewVector(w, h);
    Py_XDECREF(self->points[3]);
    self->points[3] = uNewVector(-w, h);

    // Set Orientation
    if (self->orientation != 0) {
        double j = self->orientation;
        self->orientation = 0;
        PyObject *val = Py_BuildValue("(i)", 0);
        PyObject *result = Polygon_setcache((Polygon *)self, val);
        Py_DECREF(val);
        Py_DECREF(result);

        int i;
        Vector *v;
        PyObject *o;
        double orient;

        for (i = 0; i < 4; i++) {
            v = self->points[i];
            orient = self->orients[i];
            o = PyFloat_FromDouble(j + orient);

            Vector_setangle((Vector *)v, o, CLOSURE);
            Py_DECREF(o);
        }
    }
    Py_RETURN_NONE;
}

PyObject *Rect_reduce(PyObject *self) {
    return Py_BuildValue("(O(Odddb))", self->ob_type,
                         ((Rect *)self)->center,
                         ((Rect *)self)->width,
                         ((Rect *)self)->height,
                         ((Rect *)self)->orientation,
                         ((Rect *)self)->flags);
}

/* Presentation */
PyObject *Rect_repr(Rect *self) {
    char string[64];
    sprintf(string, "<flmath.Rect([%f, %f], %f, %f)>", self->center->pt->x,
            self->center->pt->y, self->width, self->height);
    return PyString_FromString(string);
}

PyObject *Rect_str(Rect *self) {
    return Rect_repr(self);
}

/* Sequence Operations */
PyObject *Rect_item(Rect *self, Py_ssize_t i) {
    if (i >= 4 || i < 0) {
        PyErr_SetString(PyExc_IndexError, "Rect index out of range");
        return NULL;
    }
    return Vector_add((PyObject *)self->points[i], (PyObject *)self->center);
}

int Rect_ass_item(Rect *self, Py_ssize_t i, PyObject *value) {
    if (i >= 4 || i < 0) {
        PyErr_SetString(PyExc_IndexError, "Rect index out of range");
        return -1;
    }

    Vector *n = Vector_FromObject(value);
    if (n == NULL) {
        PyErr_SetString(PyExc_TypeError, "argument must be Vector-like");
        return -1;
    }

    Vector *o = (Vector *)self->points[i];
    double ox = o->pt->x + self->center->pt->x;
    double oy = o->pt->y + self->center->pt->y;

    self->center->pt->x += n->pt->x - ox;
    self->center->pt->y += n->pt->y - oy;
    Py_DECREF(n);
    return 0;
}

PyObject *Rect_mutlen(Rect *self, PyObject *args) {
    PyErr_SetString(PyExc_TypeError, "can't change length of 'flamingo.flmath.Rect'");
    return NULL;
}

/* Generic Functions */
PyObject *Rect_copy(Rect *self) {
    return (PyObject *)Polygon_FromObject((PyObject *)self);
}

/* Properties */
/* Edges */
PyObject *Rect_gettop(Rect *self, void *closure) {
    if (self->orientation != 0) {
        PyErr_SetString(PyExc_AttributeError, "can't set edges of rotated Rect.");
        return NULL;
    }
    Vector *point = self->points[3];
    PyObject *result = PyFloat_FromDouble(point->pt->y + self->center->pt->y);
    return result;
}

int Rect_settop(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC) {
        return 0;
    }

    if (self->orientation != 0) {
        PyErr_SetString(PyExc_AttributeError, "can't set edges of rotated Rect.");
        return -1;
    }

    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "value must be numerical.");
        return -1;
    }
    double v = PyFloat_AsDouble(value);
    Vector *point = self->points[3];
    self->center->pt->y += v - point->pt->y;
    return 0;
}

PyObject *Rect_getbottom(Rect *self, void *closure) {
    if (self->orientation != 0) {
        PyErr_SetString(PyExc_AttributeError, "can't set edges of rotated Rect.");
        return NULL;
    }
    Vector *point = self->points[0];
    PyObject *result = PyFloat_FromDouble(point->pt->y + self->center->pt->y);
    return result;
}

int Rect_setbottom(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    if (self->orientation != 0) {
        PyErr_SetString(PyExc_AttributeError, "can't set edges of rotated Rect.");
        return -1;
    }

    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "value must be numerical.");
        return -1;
    }
    double v = PyFloat_AsDouble(value);
    Vector *point = self->points[0];
    self->center->pt->y += v - point->pt->y;
    return 0;
}

PyObject *Rect_getleft(Rect *self, void *closure) {
    if (self->orientation != 0) {
        PyErr_SetString(PyExc_AttributeError, "can't set edges of rotated Rect.");
        return NULL;
    }
    Vector *point = self->points[0];
    PyObject *result = PyFloat_FromDouble(point->pt->x + self->center->pt->x);
    return result;
}

int Rect_setleft(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    if (self->orientation != 0) {
        PyErr_SetString(PyExc_AttributeError, "can't set edges of rotated Rect.");
        return -1;
    }

    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "value must be numerical.");
        return -1;
    }
    double v = PyFloat_AsDouble(value);
    Vector *point = self->points[0];
    self->center->pt->x += v - point->pt->x;
    return 0;
}

PyObject *Rect_getright(Rect *self, void *closure) {
    if (self->orientation != 0) {
        PyErr_SetString(PyExc_AttributeError, "can't set edges of rotated Rect.");
        return NULL;
    }
    Vector *point = self->points[1];
    PyObject *result = PyFloat_FromDouble(point->pt->x + self->center->pt->x);
    return result;
}

int Rect_setright(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    if (self->orientation != 0) {
        PyErr_SetString(PyExc_AttributeError, "can't set edges of rotated Rect.");
        return -1;
    }

    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "value must be numerical.");
        return -1;
    }
    double v = PyFloat_AsDouble(value);
    Vector *point = self->points[1];
    self->center->pt->x += v - point->pt->x;
    return 0;
}

/* Corners */
PyObject *Rect_gettopleft(Rect *self, void *closure) {
    Vector *corner = self->points[3];
    Vector *result = uNewVector(corner->pt->x + self->center->pt->x,
                                corner->pt->y + self->center->pt->y);
    return (PyObject *)result;
}

int Rect_settopleft(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    Vector *v = Vector_FromObject(value);
    if (v == NULL) {
        PyErr_SetString(PyExc_TypeError, "value must be Vector-like.");
        return -1;
    }

    Vector *corner = self->points[3];
    double ox = corner->pt->x, oy = corner->pt->y;
    ox += self->center->pt->x;
    oy += self->center->pt->y;

    self->center->pt->x += v->pt->x - ox;
    self->center->pt->y += v->pt->y - oy;
    Py_DECREF(v);
    return 0;
}

PyObject *Rect_getbottomleft(Rect *self, void *closure) {
    Vector *corner = self->points[0];
    Vector *result = uNewVector(corner->pt->x + self->center->pt->x,
                                corner->pt->y + self->center->pt->y);
    return (PyObject *)result;
}

int Rect_setbottomleft(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    Vector *v = Vector_FromObject(value);
    if (v == NULL) {
        PyErr_SetString(PyExc_TypeError, "value must be Vector-like.");
        return -1;
    }

    Vector *corner = self->points[0];
    double ox = corner->pt->x, oy = corner->pt->y;
    ox += self->center->pt->x;
    oy += self->center->pt->y;

    self->center->pt->x += v->pt->x - ox;
    self->center->pt->y += v->pt->y - oy;
    Py_DECREF(v);
    return 0;
}

PyObject *Rect_gettopright(Rect *self, void *closure) {
    Vector *corner = self->points[2];
    Vector *result = uNewVector(corner->pt->x + self->center->pt->x,
                                corner->pt->y + self->center->pt->y);
    return (PyObject *)result;
}

int Rect_settopright(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    Vector *v = Vector_FromObject(value);
    if (v == NULL) {
        PyErr_SetString(PyExc_TypeError, "value must be Vector-like.");
        return -1;
    }

    Vector *corner = self->points[2];
    double ox = corner->pt->x, oy = corner->pt->y;
    ox += self->center->pt->x;
    oy += self->center->pt->y;

    self->center->pt->x += v->pt->x - ox;
    self->center->pt->y += v->pt->y - oy;
    Py_DECREF(v);
    return 0;
}

PyObject *Rect_getbottomright(Rect *self, void *closure) {
    Vector *corner = self->points[1];
    Vector *result = uNewVector(corner->pt->x + self->center->pt->x,
                                corner->pt->y + self->center->pt->y);
    return (PyObject *)result;
}

int Rect_setbottomright(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    Vector *v = Vector_FromObject(value);
    if (v == NULL) {
        PyErr_SetString(PyExc_TypeError, "value must be Vector-like.");
        return -1;
    }

    Vector *corner = self->points[1];
    double ox = corner->pt->x, oy = corner->pt->y;
    ox += self->center->pt->x;
    oy += self->center->pt->y;

    self->center->pt->x += v->pt->x - ox;
    self->center->pt->y += v->pt->y - oy;
    Py_DECREF(v);
    return 0;
}

/* Midpoints */
PyObject *Rect_getmidtop(Rect *self, void *closure) {
    Vector *c1 = self->points[2];
    Vector *c2 = self->points[3];
    Vector *mid = uNewVector((c1->pt->x + c2->pt->x)/2.0, (c1->pt->y + c2->pt->y)/2.0);
    Vector *result = uNewVector(mid->pt->x + self->center->pt->x,
                                mid->pt->y + self->center->pt->y);
    Py_DECREF(mid);
    return (PyObject *)result;
}

int Rect_setmidtop(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    Vector *v = Vector_FromObject(value);
    if (v == NULL) {
        PyErr_SetString(PyExc_TypeError, "value must be Vector-like.");
        return -1;
    }

    Vector *c1 = self->points[2];
    Vector *c2 = self->points[3];
    Vector *mid = uNewVector((c1->pt->x + c2->pt->x)/2.0, (c1->pt->y + c2->pt->y)/2.0);
    double ox = mid->pt->x, oy = mid->pt->y;
    ox += self->center->pt->x;
    oy += self->center->pt->y;

    self->center->pt->x += v->pt->x - ox;
    self->center->pt->y += v->pt->y - oy;
    Py_DECREF(v);
    Py_DECREF(mid);
    return 0;
}

PyObject *Rect_getmidbottom(Rect *self, void *closure) {
    Vector *c1 = self->points[0];
    Vector *c2 = self->points[1];
    Vector *mid = uNewVector((c1->pt->x + c2->pt->x)/2.0, (c1->pt->y + c2->pt->y)/2.0);
    Vector *result = uNewVector(mid->pt->x + self->center->pt->x,
                                mid->pt->y + self->center->pt->y);
    Py_DECREF(mid);
    return (PyObject *)result;
}

int Rect_setmidbottom(Rect *self, PyObject *value, void *closure) {
    Vector *v = Vector_FromObject(value);
    if (v == NULL) {
        PyErr_SetString(PyExc_TypeError, "value must be Vector-like.");
        return -1;
    }

    Vector *c1 = self->points[0];
    Vector *c2 = self->points[1];
    Vector *mid = uNewVector((c1->pt->x + c2->pt->x)/2.0, (c1->pt->y + c2->pt->y)/2.0);
    double ox = mid->pt->x, oy = mid->pt->y;
    ox += self->center->pt->x;
    oy += self->center->pt->y;

    self->center->pt->x += v->pt->x - ox;
    self->center->pt->y += v->pt->y - oy;
    Py_DECREF(v);
    Py_DECREF(mid);
    return 0;
}

PyObject *Rect_getmidleft(Rect *self, void *closure) {
    Vector *c1 = self->points[3];
    Vector *c2 = self->points[0];
    Vector *mid = uNewVector((c1->pt->x + c2->pt->x)/2.0, (c1->pt->y + c2->pt->y)/2.0);
    Vector *result = uNewVector(mid->pt->x + self->center->pt->x,
                                mid->pt->y + self->center->pt->y);
    Py_DECREF(mid);
    return (PyObject *)result;
}

int Rect_setmidleft(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    Vector *v = Vector_FromObject(value);
    if (v == NULL) {
        PyErr_SetString(PyExc_TypeError, "value must be Vector-like.");
        return -1;
    }

    Vector *c1 = self->points[3];
    Vector *c2 = self->points[0];
    Vector *mid = uNewVector((c1->pt->x + c2->pt->x)/2.0, (c1->pt->y + c2->pt->y)/2.0);
    double ox = mid->pt->x, oy = mid->pt->y;
    ox += self->center->pt->x;
    oy += self->center->pt->y;

    self->center->pt->x += v->pt->x - ox;
    self->center->pt->y += v->pt->y - oy;
    Py_DECREF(v);
    Py_DECREF(mid);
    return 0;
}

PyObject *Rect_getmidright(Rect *self, void *closure) {
    Vector *c1 = self->points[1];
    Vector *c2 = self->points[2];
    Vector *mid = uNewVector((c1->pt->x + c2->pt->x)/2.0, (c1->pt->y + c2->pt->y)/2.0);
    Vector *result = uNewVector(mid->pt->x + self->center->pt->x,
                                mid->pt->y + self->center->pt->y);
    Py_DECREF(mid);
    return (PyObject *)result;
}

int Rect_setmidright(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    Vector *v = Vector_FromObject(value);
    if (v == NULL) {
        PyErr_SetString(PyExc_TypeError, "value must be Vector-like.");
        return -1;
    }

    Vector *c1 = self->points[1];
    Vector *c2 = self->points[2];
    Vector *mid = uNewVector((c1->pt->x + c2->pt->x)/2.0, (c1->pt->y + c2->pt->y)/2.0);
    double ox = mid->pt->x, oy = mid->pt->y;
    ox += self->center->pt->x;
    oy += self->center->pt->y;

    self->center->pt->x += v->pt->x - ox;
    self->center->pt->y += v->pt->y - oy;
    Py_DECREF(v);
    Py_DECREF(mid);
    return 0;
}

/* Dimensions */
PyObject *Rect_getwidth(Rect *self, void *closure) {
    return PyFloat_FromDouble(self->width);
}

int Rect_setwidth(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "value must be numerical.");
        return -1;
    }

    double v = PyFloat_AsDouble(value);
    self->width = v;
    PyObject *result = Rect_calculate_corners(self);
    Py_DECREF(result);
    PyObject *val = Py_BuildValue("(i)", 0);
    result = Polygon_setcache((Polygon *)self, val);
    Py_DECREF(val);
    Py_DECREF(result);
    return 0;
}

PyObject *Rect_getheight(Rect *self, void *closure) {
    return PyFloat_FromDouble(self->height);
}

int Rect_setheight(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "value must be numerical.");
        return -1;
    }

    double v = PyFloat_AsDouble(value);
    self->height = v;
    PyObject *result = Rect_calculate_corners(self);
    Py_DECREF(result);
    PyObject *val = Py_BuildValue("(i)", 0);
    result = Polygon_setcache((Polygon *)self, val);
    Py_DECREF(val);
    Py_DECREF(result);
    return 0;
}

PyObject * Rect_getsize(Rect *self, void *closure) {
    return Py_BuildValue("(dd)", self->width, self->height);
}

int Rect_setsize(Rect *self, PyObject *value, void *closure) {

    if (self->flags & POLYGON_STATIC)
        return 0;

    double w, h;
    if (!PyArg_ParseTuple(value, "dd", &w, &h)) {
        return -1;
    }

    self->width = w;
    self->height = h;
    PyObject *result = Rect_calculate_corners(self);
    Py_DECREF(result);
    PyObject *val = Py_BuildValue("(i)", 0);
    result = Polygon_setcache((Polygon *)self, val);
    Py_DECREF(val);
    Py_DECREF(result);
    return 0;
}

/* Method Declaration */
PyMemberDef Rect_members[] = {
    {NULL}
};

PySequenceMethods Rect_SequenceMethods = {
    0,                                          /* sq_length */
    0,                                          /* sq_concat */
    0,                                          /* sq_repeat */
    (ssizeargfunc)Rect_item,                    /* sq_item */
    0,
    (ssizeobjargproc)Rect_ass_item,             /* sq_ass_item */
    0,                                          /* sq_contains */
    0,                                          /* sq_inplace_concat */
    0,                                          /* sq_inplace_repeat */
};

PyMethodDef Rect_methods[] = {
    {"_calculate_corners", (PyCFunction)Rect_calculate_corners, METH_NOARGS, NULL},
    {"append", (PyCFunction)Rect_mutlen, METH_VARARGS, NULL},
    {"insert", (PyCFunction)Rect_mutlen, METH_VARARGS, NULL},
    {"remove", (PyCFunction)Rect_mutlen, METH_VARARGS, NULL},
    {"copy", (PyCFunction)Rect_copy, METH_NOARGS, NULL},
    {"__reduce__", (PyCFunction)Rect_reduce, METH_NOARGS, NULL},
    {NULL}
};

PyGetSetDef Rect_getsets[] = {
    {"center", (getter)Polygon_getcenter, (setter)Polygon_setcenter, NULL, rect_cent_doc},
    {"centerx", (getter)Polygon_getcenterx, (setter)Polygon_setcenterx, NULL, rect_cenx_doc},
    {"centery", (getter)Polygon_getcentery, (setter)Polygon_setcentery, NULL, rect_ceny_doc},
    {"area", (getter)Polygon_getarea, NULL, NULL, rect_area_doc},
    {"sarea", (getter)Polygon_getsarea, NULL, NULL, rect_sarea_doc},
    {"angle", (getter)Polygon_getangle, (setter)Polygon_setangle, NULL, rect_angl_doc},
    {"orientation", (getter)Polygon_getorientation, (setter)Polygon_setorientation, NULL, rect_orie_doc},

    {"top", (getter)Rect_gettop, (setter)Rect_settop, NULL, rect_top_doc},
    {"bottom", (getter)Rect_getbottom, (setter)Rect_setbottom, NULL, rect_bot_doc},
    {"left", (getter)Rect_getleft, (setter)Rect_setleft, NULL, rect_lef_doc},
    {"right", (getter)Rect_getright, (setter)Rect_setright, NULL, rect_rig_doc},
    {"topleft", (getter)Rect_gettopleft, (setter)Rect_settopleft, NULL, rect_tpl_doc},
    {"bottomleft", (getter)Rect_getbottomleft, (setter)Rect_setbottomleft, NULL, rect_btl_doc},
    {"topright", (getter)Rect_gettopright, (setter)Rect_settopright, NULL, rect_tpr_doc},
    {"bottomright", (getter)Rect_getbottomright, (setter)Rect_setbottomright, NULL, rect_btr_doc},
    {"midtop", (getter)Rect_getmidtop, (setter)Rect_setmidtop, NULL, rect_mdt_doc},
    {"midbottom", (getter)Rect_getmidbottom, (setter)Rect_setmidbottom, NULL, rect_mdb_doc},
    {"midleft", (getter)Rect_getmidleft, (setter)Rect_setmidleft, NULL, rect_mdl_doc},
    {"midright", (getter)Rect_getmidright, (setter)Rect_setmidright, NULL, rect_mdr_doc},
    {"width", (getter)Rect_getwidth, (setter)Rect_setwidth, NULL, rect_wid_doc},
    {"height", (getter)Rect_getheight, (setter)Rect_setheight, NULL, rect_hei_doc},
    {"size", (getter)Rect_getsize, (setter)Rect_setsize, NULL, rect_siz_doc},
    {NULL}
};

/* -------- TYPE DECLARATIONS -------- */
PyTypeObject Vector_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.flmath.Vector",                   /* tp_name */
    sizeof(Vector),                             /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Vector_dealloc,                 /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Vector_repr,                      /* tp_repr */
    &Vector_NumberMethods,                      /* tp_as_number */
    &Vector_SequenceMethods,                    /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Vector_str,                       /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    VECTOR_DOC,                                  /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    (richcmpfunc)Vector_richcompare,        /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Vector_methods,                             /* tp_methods */
    Vector_members,                             /* tp_members */
    Vector_getsets,                             /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Vector_init,                      /* tp_init */
    0,                                          /* tp_alloc */
    Vector_new,                                 /* tp_new */
};

PyTypeObject Polygon_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.flmath.Polygon",                  /* tp_name */
    sizeof(Polygon),                            /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Polygon_dealloc,                /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Polygon_repr,                     /* tp_repr */
    0,                                          /* tp_as_number */
    &Polygon_SequenceMethods,                   /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Polygon_str,                      /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    POLYGON_DOC,                                /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    (richcmpfunc)Polygon_richcompare,       /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Polygon_methods,                            /* tp_methods */
    Polygon_members,                            /* tp_members */
    Polygon_getsets,                            /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Polygon_init,                     /* tp_init */
    0,                                          /* tp_alloc */
    Polygon_new,                                /* tp_new */
};

PyTypeObject Rect_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.flmath.Rect",                     /* tp_name */
    sizeof(Rect),                               /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Rect_dealloc,                   /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Rect_repr,                        /* tp_repr */
    0,                                          /* tp_as_number */
    &Rect_SequenceMethods,                      /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Rect_str,                         /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    RECT_DOC,                                   /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    (richcmpfunc)Polygon_richcompare,       /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Rect_methods,                               /* tp_methods */
    Rect_members,                               /* tp_members */
    Rect_getsets,                               /* tp_getset */
    &Polygon_Type,                              /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Rect_init,                        /* tp_init */
    0,                                          /* tp_alloc */
    Rect_new,                                   /* tp_new */
};

/* -------- MODULE INIT -------- */
PyMethodDef flmath_methods[] = {
    {"closest_pow2", closest_pow2, METH_VARARGS, cpow2_doc},
    {"next_pow2", next_pow2, METH_VARARGS, npow2_doc},
    {"prev_pow2", prev_pow2, METH_VARARGS, ppow2_doc},
    {"normalize_angle", normalize_angle, METH_VARARGS, nangd_doc},
    {"normalize_angler", normalize_angler, METH_VARARGS, nangr_doc},
    {"distance", distance, METH_VARARGS, dist_doc},
    {"distance_sq", distance_sq, METH_VARARGS, dist2_doc},
    {"midpoint", midpoint, METH_VARARGS, midpt_doc},
    {"angle_between", angle_between, METH_VARARGS, angbt_doc},
    {"dot", dot, METH_VARARGS, dotpr_doc},
    {"normalize_poly", normalize_poly, METH_VARARGS, npoly_doc},
    {"point_in", point_in, METH_VARARGS, pntin_doc},
    {"centroid", centroid, METH_VARARGS, centr_doc},
    {"area", area, METH_VARARGS, area_doc},
    {"signed_area", signed_area, METH_VARARGS, sarea_doc},
    {"orientation_poly", orientation_poly, METH_VARARGS, orien_doc},
    //{"simplify_poly", simplify_poly, METH_VARARGS, simpl_doc},
    {NULL}
};

PyMODINIT_FUNC
initflmath(void)
{
    PyObject *m;

    if (PyType_Ready(&Vector_Type) < 0)
        return;
    if (PyType_Ready(&Polygon_Type) < 0)
        return;
    if (PyType_Ready(&Rect_Type) < 0)
        return;

    m = Py_InitModule3("flmath", flmath_methods, FLMATH_DOC);
    if (m == NULL)
        return;

    Py_INCREF(&Vector_Type);
    PyModule_AddObject(m, "Vector", (PyObject *)&Vector_Type);
    Py_INCREF(&Polygon_Type);
    PyModule_AddObject(m, "Polygon", (PyObject *)&Polygon_Type);
    Py_INCREF(&Rect_Type);
    PyModule_AddObject(m, "Rect", (PyObject *)&Rect_Type);
}
